/*
 * Copyright 2007-2008 Andrew O'Malley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package ws.quokka.plugin.xml;

import org.apache.tools.ant.taskdefs.XSLTProcess;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.ResourceLocation;
import org.apache.tools.ant.types.XMLCatalog;
import org.apache.tools.ant.types.resources.FileResource;

import ws.quokka.core.metadata.Metadata;
import ws.quokka.core.metadata.MetadataAware;
import ws.quokka.core.plugin_spi.support.AbstractPlugin;
import ws.quokka.core.plugin_spi.support.Setter;
import ws.quokka.core.plugin_spi.support.TypedProperties;
import ws.quokka.core.repo_spi.RepoArtifact;
import ws.quokka.core.util.URLs;
import ws.quokka.core.util.xml.Document;
import ws.quokka.core.util.xml.Element;

import java.io.File;

import java.net.URL;

import java.util.Iterator;
import java.util.List;
import java.util.Map;


/**
 *
 */
public class XmlPlugin extends AbstractPlugin implements MetadataAware {
    //~ Instance fields ------------------------------------------------------------------------------------------------

    private Metadata metadata;

    //~ Methods --------------------------------------------------------------------------------------------------------

    public void setMetadata(Metadata metadata) {
        this.metadata = metadata;
    }

    public void transform() {
        TypedProperties properties = properties();
        XSLTProcess xsltTask = (XSLTProcess) utils().init(new XSLTProcess(), "xslt");

        File outFile = properties.getFile("out", null);

        if (outFile != null) {
            utils().mkdir(outFile.getParentFile());
            xsltTask.setOut(outFile);
        }

        File destDir = properties.getFile("destDir", null);

        if (destDir != null) {
            utils().mkdir(destDir);
            xsltTask.setDestdir(destDir);
        }

        Setter setter = new Setter(properties);
        setter.set(xsltTask,
            new String[] {
                "extension", "defaultExcludes", "baseDir", "includes", "excludes", "force", "includesFile",
                "excludesFile", "scanIncludedDirectories"
            });

        // Allow setting of a custom classpath for transforms (e.g. using saxon)
        String classpathId = properties().getString("transformClassPath", null);

        if (classpathId != null) {
            xsltTask.setClasspath(getResources().getProjectPath(classpathId));
        }

        xsltTask.setXslResource(new FileResource(properties.getFile("style")));
        xsltTask.setIn(properties.getFile("in", null));

        String factoryName = properties().getString("factory", null);

        if (factoryName != null) {
            XSLTProcess.Factory factory = xsltTask.createFactory();
            factory.setName(factoryName);

            Map params = properties().getMap("factory.params", false, String.class);

            for (Iterator i = params.entrySet().iterator(); i.hasNext();) {
                Map.Entry entry = (Map.Entry)i.next();
                XSLTProcess.Factory.Attribute attribute = new XSLTProcess.Factory.Attribute();
                attribute.setDynamicAttribute((String)entry.getKey(), (String)entry.getValue());
                factory.addAttribute(attribute);
            }
        }

        Map params = properties.getMap("params", false, String.class);

        for (Iterator i = params.entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry)i.next();
            XSLTProcess.Param param = xsltTask.createParam();
            param.setName((String)entry.getKey());
            param.setExpression((String)entry.getValue());
        }

        Map outputProperties = properties.getMap("outputProperty", false, String.class);

        for (Iterator i = outputProperties.entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry)i.next();
            XSLTProcess.OutputProperty outputProperty = xsltTask.createOutputProperty();
            outputProperty.setName((String)entry.getKey());
            outputProperty.setValue((String)entry.getValue());
        }

        XMLCatalog catalog = createXMLCatalog(properties);

        if (catalog != null) {
            xsltTask.addConfiguredXMLCatalog(catalog);
        }

        xsltTask.perform();
    }

    private XMLCatalog createXMLCatalog(TypedProperties properties) {
        // Allow setting of a custom classpath for transforms (e.g. using saxon)
        String pathId = properties.getString("catalogPath", null);

        if (pathId == null) {
            return null;
        }

        XMLCatalog catalog = new XMLCatalog();
        catalog.setProject(getProject());

        for (Iterator i = metadata.getProjectPath(pathId).iterator(); i.hasNext();) {
            parseCatalog((RepoArtifact)i.next(), catalog);
        }

        return catalog;
    }

    private void parseCatalog(RepoArtifact artifact, XMLCatalog catalog) {
        // Add the jar to the catalog class path to all
        File catalogJar = artifact.getLocalCopy();
        catalog.setClasspath(new Path(getProject(), catalogJar.getAbsolutePath()));

        // Parse the catalogue xml file (a subset of oasis repo_standard)
        String root = "META-INF/quokka/" + artifact.getId().toPathString() + "/";
        URL url = URLs.toURL(catalogJar, root + "catalog.xml");
        assertTrue(url != null,
            catalogJar.getAbsolutePath() + " does not contain catalog file '" + root + "catalog.xml'");

        Document document = Document.parse(url);
        Element rootEl = document.getRoot();
        List publicEls = rootEl.getChildren("public");
        root += "catalog/";

        for (Iterator i = publicEls.iterator(); i.hasNext();) {
            Element publicEl = (Element)i.next();
            catalog.addDTD(createLocation(catalogJar, publicEl.getAttribute("publicId"),
                    root + publicEl.getAttribute("uri")));
        }

        List uriEls = rootEl.getChildren("uri");

        for (Iterator i = uriEls.iterator(); i.hasNext();) {
            Element uriEl = (Element)i.next();
            catalog.addEntity(createLocation(catalogJar, uriEl.getAttribute("name"), root + uriEl.getAttribute("uri")));
        }
    }

    private ResourceLocation createLocation(File catalogJar, String publicId, String location) {
        ResourceLocation loc = new ResourceLocation();
        loc.setPublicId(publicId);
        loc.setLocation(location);
        log().verbose("Adding xml catalog entry: id=" + publicId + ", location=" + location);

        URL url = URLs.toURL(catalogJar, location);
        assertTrue(url != null, catalogJar.getAbsolutePath() + " does not contain entity '" + location + "'");

        return loc;
    }
}
